/*
 *  Copyright (c) 1998, Regents of the University of California
 *  All rights reserved.
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *      * Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 *      * Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in the
 *        documentation and/or other materials provided with the distribution.
 *      * Neither the name of the University of California, Berkeley nor the
 *        names of its contributors may be used to endorse or promote products
 *        derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 *  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.demis.orc;

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.demis.elf.Column;
import org.demis.elf.ForeignKey;
import org.demis.elf.PrimaryKey;
import org.demis.elf.Schema;
import org.demis.troll.ClassDescriptor;
import org.demis.troll.IdentDescriptor;
import org.demis.troll.PackageDescriptor;
import org.demis.troll.PackageManager;
import org.demis.troll.RelationDescriptor;
import org.demis.elf.Table;
import org.demis.troll.PropertyDescriptor;

/**
 * @version 1.0
 * @author <a href="mailto:demis27@demis27.net">Stéphane kermabon</a>
 */
public class DataBaseToObjectConverter {

    private static final Log logger = LogFactory.getLog(DataBaseToObjectConverter.class);

    public static List<ClassDescriptor> convertSchema(Schema schema) {
        if (logger.isInfoEnabled()) {
            logger.info("convert schema #" + schema);
        }

        List<ClassDescriptor> classes = new ArrayList<ClassDescriptor>();

        for (Table table: schema.getTables()) {
            ClassDescriptor classDescriptor = convertTable(table);
            classes.add(classDescriptor);
            convertColumns(table);
            if (table.getPrimaryKey() != null) {
                convertPrimaryKey(table.getPrimaryKey());
            }

        }

        for (Table table: schema.getTables()) {
            for (ForeignKey foreignKey: table.getExportedKeys()) {
                convertFromReference(foreignKey);
            }
            for (ForeignKey foreignKey: table.getImportedKeys()) {
                convertToReference(foreignKey);
            }
        }

        return classes;
    }


    /**
     * Convert a Table to a ClassDescriptor.
     * <ul>
     * <li>table.name -> class.name</li>
     * </ul>
     * @param table The table to convert in class descriptor
     * @return A new class descriptor .
     */
    public static ClassDescriptor convertTable(Table table) {
        ClassDescriptor classDescriptor = new ClassDescriptor();

        classDescriptor.setClassName(convertDataBaseName(table.getName()));
        Mapping.getInstance().addMapping(table, classDescriptor);

        if (logger.isInfoEnabled()) {
           logger.info("table #" + table + " converted to class #" + classDescriptor);
        }

        return classDescriptor;
    }

    public static PropertyDescriptor convertColumn(Column column) {
        PropertyDescriptor propertyDescriptor = new PropertyDescriptor();
        // Process property name
        String propertyName = convertDataBaseName(column.getName(), !column.isPrimaryKey());
        propertyName = propertyName.substring(0,1).toLowerCase() + propertyName.substring(1,propertyName.length());
        propertyDescriptor.setName(propertyName);
        // Process java type
        propertyDescriptor.setJavaType(SQLTypeToClassConverter.convert(column.getType()));
        propertyDescriptor.setContributeToIdent(column.isPrimaryKey());
        propertyDescriptor.setContributeToRelation(column.isForeignKey());
        Mapping.getInstance().addMapping(column, propertyDescriptor);

        return propertyDescriptor;
    }

    public static List<PropertyDescriptor> convertColumns(Table table) {
        List<PropertyDescriptor> properties = new ArrayList<PropertyDescriptor>();
        for (Column column : table.getColumns()) {
            PropertyDescriptor propertyDescriptor = convertColumn(column);
            properties.add(propertyDescriptor);
            Mapping.getInstance().getClassDescriptor(table).addProperty(propertyDescriptor);
            if (logger.isInfoEnabled()) {
               logger.info("column #" + column + " converted to property #" + propertyDescriptor);
            }
            Mapping.getInstance().addMapping(column, propertyDescriptor);
        }

        return properties;
    }

    public static IdentDescriptor convertPrimaryKey(PrimaryKey primaryKey) {
        IdentDescriptor identDecriptor = new IdentDescriptor();
        // ident name
        String name = null;
        String className = null;
        if (primaryKey.nbColumn() > 1) {
            name = convertDataBaseName(primaryKey.getTable().getName()) + "PrimaryKey";
            className = name;
        }
        else {
            name = convertDataBaseName(primaryKey.getColumnAs(1).getName(), false);
	    className = Mapping.getInstance().getPropertyDescriptor(primaryKey.getColumnAs(1)).getJavaType();
        }

        name = toLowerCaseFirst(name);

        for (Column column : primaryKey.getColumns()) {
            column.setForeignKey(true);
            identDecriptor.addProperty(Mapping.getInstance().getPropertyDescriptor(column));
        }
        identDecriptor.setName(name);
        identDecriptor.setClassName(className);
        
        ClassDescriptor classDescriptor = Mapping.getInstance().getClassDescriptor(primaryKey.getTable());
        classDescriptor.setIdent(identDecriptor);

        if (logger.isInfoEnabled()) {
           logger.info("primaryKey #" + primaryKey + " converted to ident #" + identDecriptor);
        }
        return identDecriptor;
    }

    public static RelationDescriptor convertFromReference(ForeignKey reference) {
        RelationDescriptor relation = new RelationDescriptor();
        relation.setLeftDescriptor(Mapping.getInstance().getClassDescriptor(reference.getImportedTable()));
        relation.setRightDescriptor(Mapping.getInstance().getClassDescriptor(reference.getExportedTable()));

        // from properties
        for (Column fromColumn : reference.getImportedColumns()) {
            Column toColumn = reference.getExportedColumn(fromColumn);

            PropertyDescriptor leftPropertyDescriptor = Mapping.getInstance().getPropertyDescriptor(fromColumn);
            PropertyDescriptor rightPropertyDescriptor = Mapping.getInstance().getPropertyDescriptor(toColumn);
            relation.addProperties(leftPropertyDescriptor, rightPropertyDescriptor);
        }
        // name
        if (reference.getExportedTable() != null) {
            relation.setName(toLowerCaseFirst(convertDataBaseName(reference.getName())));
        }

        ClassDescriptor classDescriptor = Mapping.getInstance().getClassDescriptor(reference.getImportedTable());
        classDescriptor.addLeftRelation(relation);

        if (logger.isInfoEnabled()) {
           logger.info("'from' reference #" + reference + " converted to relation #" + relation);
        }
        return relation;
    }

    public static RelationDescriptor convertToReference(ForeignKey reference) {
        RelationDescriptor relation = new RelationDescriptor();
        relation.setLeftDescriptor(Mapping.getInstance().getClassDescriptor(reference.getImportedTable()));
        relation.setRightDescriptor(Mapping.getInstance().getClassDescriptor(reference.getExportedTable()));

        // from properties
        for (Column fromColumn : reference.getImportedColumns()) {
            Column toColumn = reference.getExportedColumn(fromColumn);

            PropertyDescriptor leftPropertyDescriptor = Mapping.getInstance().getPropertyDescriptor(fromColumn);
            PropertyDescriptor rightPropertyDescriptor = Mapping.getInstance().getPropertyDescriptor(toColumn);
            relation.addProperties(leftPropertyDescriptor, rightPropertyDescriptor);
        }
        // name
        if (reference.getImportedColumns().size() == 1) {
            Column fromColumn = reference.getImportedColumns().iterator().next();
            relation.setName(toLowerCaseFirst(convertDataBaseName(fromColumn.getName())));
        }

        ClassDescriptor classDescriptor = Mapping.getInstance().getClassDescriptor(reference.getExportedTable());
        classDescriptor.addRightRelation(relation);

        if (logger.isInfoEnabled()) {
           logger.info("'to' reference #" + reference + " converted to relation #" + relation);
        }
        return relation;
    }

    public static IdentDescriptor convertPrimaryKey(ClassDescriptor classDescriptor, PrimaryKey primaryKey) {
        IdentDescriptor identDecriptor = new IdentDescriptor();
        // ident name
        String name = null;
        if (primaryKey.nbColumn() > 1) {
            name = classDescriptor.getClassName() + "PrimaryKey";
        }
        else {
            name = convertDataBaseName(primaryKey.getColumnAs(0).getName(), false);
        }

        name = toLowerCaseFirst(name);

        for (Column column : primaryKey.getColumns()) {
            column.setForeignKey(true);
        }

        return identDecriptor;
    }

    private static void processProperties(Table table, ClassDescriptor classDescriptor) {
        // simple properties
        for (Column column : table.getColumns()) {
            PropertyDescriptor PropertyDescriptor = convertColumn(column);
            PropertyDescriptor.setClassDescriptor(classDescriptor);
        }
        // ident descriptor
        if (table.getPrimaryKey() != null) {
            IdentDescriptor identDecriptor = convertPrimaryKey(classDescriptor, table.getPrimaryKey());
            classDescriptor.setIdent(identDecriptor);
        }
    }

    public static String convertDataBaseName(String name) {
        return convertDataBaseName(name, true);
    }

    public static String convertDataBaseName(String name, boolean removeIdinName) {
        String baseName = name.toLowerCase();
        StringTokenizer tokenizer = new StringTokenizer(baseName, "_");
        StringBuffer buffer = new StringBuffer("");
        while (tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            if (!(token.equalsIgnoreCase("id") && removeIdinName && !tokenizer.hasMoreTokens())) {
                buffer.append(token.substring(0, 1).toUpperCase());
                buffer.append(token.substring(1, token.length()).toLowerCase());
            }
        }
        return buffer.toString();
    }

    public static String toUpperCaseFirst(String value) {
        if (value == null || value.length() == 0) {
            return value;
        }
        return value.substring(0, 1).toUpperCase() +  value.substring(1, value.length());
    }

    public static String toLowerCaseFirst(String value) {
        if (value == null || value.length() == 0) {
            return value;
        }
        return value.substring(0, 1).toLowerCase() +  value.substring(1, value.length());
    }
}
